自作アプリからSNSアプリへ画像を共有する最適なフォーマットは?UIImage、Data、URL の比較調査した
一般的にアプリ間連携はUIActivityViewController
を利用して行われるが、自作のiOS アプリから画像や動画を共有しても、受信側のアプリがそれを正しく受け取るかどうかはわからない。画像または動画を共有する際には、受信側(共有先)のアプリが当該のフォーマットに対応しているかを調査した。
調査の背景
2週間前にiPhoneからTwitter(現X)に画像と動画を共有するアプリを公開した。ありがたいことにリリース直後から好評で、ユーザーが増えるにつれて、MastodonやBlueskyへの投稿を望む声が増えた。
その時点では、Twitterへの画像共有は、画像のファイルパスを示したURLを渡していたが、Blueskyアプリに対して、この方法で画像を共有すると、文字列としてファイルパスが表示されてしまうことがわかった。
特定のアプリに対して、どのフォーマットで画像を渡せばよいか調査を行うことにした。
iOS アプリから各SNSアプリへ画像を共有するのに最適なフォーマットはなにか
各アプリでの動作確認結果を以下にまとめた。私のアプリにて利用している「画像とテキスト(ハッシュタグ)」または「動画とテキスト(ハッシュタグ)」の組み合わせで調査した。
検証環境
- iOS 17.5
カメラロールへの保存
以下の動作確認を行った。
メディアのPlaceholder | アイテム | テキストのPlaceholder | アイテム | 結果 | 備考 | |
---|---|---|---|---|---|---|
画像+テキスト | URL | URL | String | String | o | |
画像+テキスト | UIImage | UIImage | String | String | o | |
画像+テキスト | UIImage | URL | String | String | o | |
動画+テキスト | URL | URL | String | String | o |
カメラロールへの保存なのでテキストデータは無視されることを確認した。また他のアプリでは正常に動作しない 動画+画像+テキスト
のケースでも正しくデータが処理されることを確認している。
どんなフォーマットでも処理できるのは、さすが標準アプリたる所以である。
Twitter(現X)への共有
X v10.50 (12)
にて、以下の動作確認をおこなった。
メディアのPlaceholder | アイテム | テキストのPlaceholder | アイテム | 結果 | 備考 | |
---|---|---|---|---|---|---|
画像+テキスト | URL | URL | String | String | o | |
画像+テキスト | UIImage | UIImage | String | String | o | |
画像+テキスト | UIImage | URL | String | String | o | |
動画+テキスト | URL | URL | String | String | o |
Twitterへの共有では、動画+画像+テキスト
の場合には静止画が無視されてしまうようだ。注意したい。
Blueskyへの共有
Bluesky v1.88.0.366
にて、以下の動作確認を行った。
メディアのPlaceholder | アイテム | テキストのPlaceholder | アイテム | 結果 | 備考 | |
---|---|---|---|---|---|---|
画像+テキスト | URL | URL | String | String | x | 画像のファイルパスが表示されてしまう |
画像+テキスト | UIImage | UIImage | String | String | x | BlueSkyアプリがクラッシュする |
画像+テキスト | UIImage | UIImage | String | nil |
o | テキスト表示されない |
画像+テキスト | UIImage | URL | String | String | x | BlueSkyアプリがクラッシュする |
画像+テキスト | UIImage | URL | String | nil |
x | 画像のファイルパスが表示されてしまう |
動画+テキスト | URL | URL | String | String | x | 動画の場合は候補に出てこない |
動画+テキスト | URL | URL | String | nil |
x | 動画の場合は候補に出てこない |
Blueskyアプリ側の問題だと思うが、画像とテキスト(ハッシュタグ)の両方を同時に共有することができない。エラーダイアログや、UIActivityViewController からのエラーが返ってこずにBlueskyの共有エクステンションがクラッシュしたような挙動となる。
Mastodonへの共有
Mastodon for iOS v2024.7 (6466)
にて、以下の動作確認を行った。
メディアのPlaceholder | アイテム | テキストのPlaceholder | アイテム | 結果 | 備考 | |
---|---|---|---|---|---|---|
画像+テキスト | URL | URL | String | String | ▲ | 画像は表示されるが、画像のファイルパスも表示されてしまう |
画像+テキスト | UIImage | UIImage | String | String | x | 画像が表示されない |
画像+テキスト | UIImage | URL | String | String | ▲ | 画像は表示されるが、画像のファイルパスも表示されてしまう |
画像+テキスト | UIImage | Data | String | String | o | 画像が表示される |
動画+テキスト | URL | URL | String | String | ▲ | 画像は表示されるが、画像のファイルパスも表示されてしまう |
Mastodonアプリは、UIImageを渡すことができず、ファイルパスのURLにて共有する必要があるが、URLを共有するとファイルパスを表示してしまう問題がある。PlaceholderにUIImageを設定しておき、アイテムとしてDataを渡すと問題なく画像が表示される。
Threadsへの共有
Threads v340.0
にて、以下の動作確認を行った。
メディアのPlaceholder | アイテム | テキストのPlaceholder | アイテム | 結果 | 備考 | |
---|---|---|---|---|---|---|
画像+テキスト | URL | URL | String | String | x | 画像のファイルパスが表示されてしまう |
画像+テキスト | UIImage | UIImage | String | String | ▲ | テキストが表示されない |
画像+テキスト | UIImage | URL | String | String | x | 画像のファイルパスが表示されてしまう |
動画+テキスト | URL | URL | String | String | x | 動画が無視される |
Threadsアプリに画像とテキストを同時に共有した場合、テキストが無視されてしまう。また動画の共有ができない。
Instagramへの共有
Instagram v340.0
にて、以下の動作確認を行った。
メディアのPlaceholder | アイテム | テキストのPlaceholder | アイテム | 結果 | 備考 | |
---|---|---|---|---|---|---|
画像+テキスト | URL | URL | String | String | x | エラーダイアログが表示される |
画像+テキスト | UIImage | UIImage | String | String | x | エラーダイアログが表示される |
画像+テキスト | UIImage | URL | String | String | x | エラーダイアログが表示される |
画像+テキスト | UIImage | URL | String | nil | o | |
動画+テキスト | URL | URL | String | String | x | エラーダイアログが表示される |
動画+テキスト | URL | URL | String | nil | o |
画像とテキストを共有しようとすると「複数の写真と動画をシェアできるのは Instagram アプリからのみです。」とエラーダイアログが表示される。
各SNSアプリに対して適切なフォーマットで送るためにはどうすればよいか
執筆時点での他アプリへの共有時の挙動について調査した。
それぞれのSNSアプリに対応できるように、以下のように共有先に応じて activityViewController(_:, itemForActivityType:)
で返す型を変える ImageFileActivityItem
を実装した。
import LinkPresentation
import UIKit
import UniformTypeIdentifiers
extension UIActivity.ActivityType {
static let bluesky = UIActivity.ActivityType(rawValue: "xyz.blueskyweb.app.Share-with-Bluesky")
static let threads = UIActivity.ActivityType(rawValue: "com.burbn.barcelona.ShareExtension")
static let instagram1 = UIActivity.ActivityType(rawValue: "com.burbn.instagram.shareextension")
static let instagram2 = UIActivity.ActivityType(rawValue: "com.instagram.shareextension")
static let instagram3 = UIActivity.ActivityType(rawValue: "com.instagram.exclusivegram")
static let mastdon = UIActivity.ActivityType(rawValue: "org.joinmastodon.app.ShareActionExtension")
}
class ImageFileActivityItem: NSObject, UIActivityItemSource {
let fileUrl: URL
let fileName: String
let image: UIImage?
init(fileUrl: URL, image: UIImage?) {
self.fileUrl = fileUrl
self.image = image
fileName = fileUrl.lastPathComponent
}
func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any {
// Bluesky へ共有するためにプレースホルダーにUIImageを指定する必要がある
image ?? UIImage()
}
func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return switch activityType {
case .bluesky, .threads:
// Blueskyアプリは URL を渡しても処理ができない
image
case .mastdon:
// MastdonアプリにURLを渡すと画像+ファイルパスが共有され、
// UIImage を渡すとアップロードエラーが発生するため、Data を渡す
try? Data(contentsOf: fileUrl)
default:
fileUrl
}
}
func activityViewController(_: UIActivityViewController, subjectForActivityType _: UIActivity.ActivityType?) -> String {
fileName
}
func activityViewController(_: UIActivityViewController, dataTypeIdentifierForActivityType _: UIActivity.ActivityType?) -> String {
UTType.image.identifier
}
func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? {
guard let image = image else { return nil }
let metadata = LPLinkMetadata()
metadata.title = fileName
metadata.imageProvider = NSItemProvider(object: image)
return metadata
}
}
class HashTagActivityItem: NSObject, UIActivityItemSource {
let hashTag: String
init(hashTag: String) {
self.hashTag = hashTag
}
func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any {
return hashTag
}
func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
return switch activityType {
case .bluesky:
// Bluesky へ共有する時には画像とテキストを両方投稿することができないためハッシュタグを空で送る
nil
case .instagram1, .instagram2, .instagram3:
// Instagram へ共有する時には画像とテキストを両方投稿することができないためハッシュタグを空で送る
nil
default:
hashTag
}
}
}
まとめ
どのようなフォーマットでもTwitterアプリは正しく受け取ってくれる。その反面、Twitter以外のアプリでは対応しているフォーマットがまちまちであった。
本記事では、UIActivityItemSource protocol
を実装した ImageFileActivityItem
を使い、activityViewController(_:, itemForActivityType:)
のタイミングで、適切なフォーマットに変換した上でデータを渡す方法を紹介した。